package com.webserver;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.PrintWriter;
import java.math.BigInteger;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.Principal;
import java.security.PublicKey;
import java.security.SignatureException;
import java.security.cert.CertificateException;
import java.security.cert.CertificateExpiredException;
import java.security.cert.CertificateFactory;
import java.security.cert.CertificateNotYetValidException;
import java.security.cert.X509Certificate;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Date;
import java.util.Random;

import org.itr_rescue.dataGuard.encryption.asymmetric.RSACrypto;


public class LoginWebServer {
	public static final String CAServerIP = "";
	public static final int CAServerPort = 10001;
	public static final int CAServerFilePort = 10008;
	public static final int CERTERROR_EXPIRATION = -1;
	
	public static void main(String args[]) throws CertificateException,
		IOException {
		
		
	try{  
	   ServerSocket server = new ServerSocket(10002);    //포트(10002)를 열고 기다린다...
	   System.out.println("접속을 기다립니다."); 
	   
	   while(true){                       
	    Socket sock = server.accept();    // 입력받은 클라이언트의  소켓을 생성한다...
	    EchoThread echothread = new EchoThread(sock);    //사용자의 정의 클래스 EchoThread
	    echothread.start();         // 쓰레드를 돌린다..
	   } // while 
	  }catch(Exception e){  
	   System.out.println(e); 
	  } 
		/* 
		 * FLOW
		 * way1. 사용자 등록
		 * 1-1. clientCert 수신
		 * 1-2. CACert 수신
		 * 1-3. CACert로 clientCert 검증
		 * 1-4. clientCert 의 공개키로 임의의 인증메시지 암호화
		 * 1-5. client에게 전송
		 * 1-6. client로부터 인증메시지 수신
		 * 1-7. 인증메시지 확인
		 * 1-8. DB에 사용자 등록
		 * way2. 사용자 로그인
		 * 2-1. clientCert 수신
		 * 2-2. 보관하고있는 CACert로 clientCert 검증
		 * 2-3. 1-4 ~ 1-7 수행
		 * 2-4. 로그인
		 * 
		 */
	}
}

class EchoThread extends Thread { // 쓰레드구현을 위해 쓰레드를 상속..
	private Socket socket;
//	OutputStream out;
	ObjectInputStream ois;
	ObjectOutputStream oos;
	File fileClientCert;
	File fileCaCert;
	X509Certificate clientCert;
	X509Certificate caCert;
	final int serverFilePort = 10005;
	final int authmsgFilePort = 10006;

	public EchoThread(Socket sock) { // 생성자 구현..
		this.socket = sock;
		fileClientCert = new File("ClientCert.der");
		fileCaCert = new File("CACert.der");
		
		try {
			ois = new ObjectInputStream(sock.getInputStream());
			oos = new ObjectOutputStream(socket.getOutputStream());
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	public void run() {
		try {
			InetAddress inetaddr = socket.getInetAddress(); // 접속된 소켓의 ip주소값을 가져온다..
			System.out.println(inetaddr.getHostAddress() + " 로 부터 접속하였습니다."); // 호스트주소값을 가져온다..
			/** 메시지 읽어들임 **/
			System.out.println("대기중");
			Object obj;
			while(true){
				obj = ois.readObject();
				if (obj.getClass() == String.class) {
					String msg = (String) obj;	// msg는 LoginRequest와 RegisterUser 를 구분하기 위해 쓰인다. 
					
					if (msg.equals("LoginRequest")) {
						if(!checkUserCert()){
							System.out.println("개인인증 실패");
							continue;
						}
						// DB의 LOGIN IP 테이블에 클라이언트의 IP를 등록하여 웹서버에 접속가능하게 한다.
						try {
							
							Class.forName("oracle.jdbc.driver.OracleDriver");
				        	Connection conn;
							conn = DriverManager.getConnection("jdbc:oracle:thin:@127.0.0.1:1521:XE","darkntem","jk");
							System.out.println("DB Access complete");
				        	Statement st = conn.createStatement();
				        	
				        	BigInteger certSerialNumber = clientCert.getSerialNumber();
				        	ResultSet rs = st.executeQuery("select * from USERINFO where SERIALNUMBER = '" + certSerialNumber + "'");
				        	if(rs.next() == true){
				        		System.out.println("회원정보 확인 완료");
				        		//이미 로그인 되어있는지 확인
				        		rs = st.executeQuery("select * from ACCESSIP where CERTSERIALNUMBER = '" + certSerialNumber + "'");
				        		if(rs.next() == true){
				        			//기존의 로그인 제거 후 로그인 
				        			st.executeUpdate("delete from ACCESSIP where IP='" + socket.getInetAddress().getHostAddress() + "'");
				        			st.executeUpdate("insert into ACCESSIP values ('" + socket.getInetAddress().getHostAddress() + "', + '" + certSerialNumber + "')");
				        		}else{
				        			st.executeUpdate("insert into ACCESSIP values ('" + socket.getInetAddress().getHostAddress() + "', + '" + certSerialNumber + "')");
				        		}
				        		oos.writeObject("LoginSuccess");
				        	}else{
				        		System.out.println("가입되지 않은 회원");
				        		oos.writeObject("NotRegisteredUserException");
				        	}
						} catch (SQLException e) {
							e.printStackTrace();
						}
			        	
					} else if (msg.equals("RegisterUser")) {
						if(!checkUserCert()){
							System.out.println("개인인증 실패");
							continue;
						}
						// DB의 USERINFOMAION 테이블에 사용자 등록한다
						try{
				        	Class.forName("oracle.jdbc.driver.OracleDriver");
				        	Connection conn = DriverManager.getConnection("jdbc:oracle:thin:@127.0.0.1:1521:XE","darkntem","jk");
				        	System.out.println("DB Access complete");
				        	Statement st = conn.createStatement();
				        	
				        	BigInteger certSerialNumber = clientCert.getSerialNumber();
				        	Principal userInfo = clientCert.getSubjectDN();
				        	
				        	String userEmail = userInfo.getName().split(",")[0].split("=")[1];
				        	String userName = userInfo.getName().split(",")[1].split("=")[1];
				        	System.out.println(userEmail);
				        	System.out.println(certSerialNumber.intValue());
				        	
				        	ResultSet rs = st.executeQuery("select * from USERINFO where SERIALNUMBER = '" + certSerialNumber + "'");
				        	if(rs.next() == true){
				        		System.out.println("가입한적 있음");
				        		oos.writeObject("AlreadRegisteredException");
				        	}else{
				        		System.out.println("아무것도 없음");
				        		st.executeUpdate("insert into USERINFO values (" + certSerialNumber + ",'" + userName + "','" + userEmail + "')");
					        	st.executeUpdate("insert into ACCESSIP values ('" + socket.getInetAddress().getHostAddress() + "', + '" + certSerialNumber + "')");
					        	oos.writeObject("RegisterUserSuccess");
				        	}
				        }catch(ClassNotFoundException e){
				        	System.err.println("ClassNotFoundException");
				        } catch (SQLException e) {
							e.printStackTrace();
						}
					} else if (msg.equals("Logout")){
						// DB의 LOGIN IP 테이블에 클라이언트의 IP를 삭제한다.
						try {
							Class.forName("oracle.jdbc.driver.OracleDriver");
				        	Connection conn;
							conn = DriverManager.getConnection("jdbc:oracle:thin:@127.0.0.1:1521:XE","darkntem","jk");
							System.out.println("DB Access complete");
				        	Statement st = conn.createStatement();
				        	
				        	st.executeUpdate("delete from ACCESSIP where IP='" + socket.getInetAddress().getHostAddress() + "'");
						} catch (SQLException e) {
							e.printStackTrace();
						}
					}
					
				}
			}
		} catch (IOException e) {
			System.out.println("logout");
			//e.printStackTrace();
			
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		}
		

	}

	private boolean checkUserCert() {
		// LoginRequest와 RegisterUser 절차의 공통적인 부분인 개인인증서, CA인증서, userVerify를 수행한다.
		System.out.println("개인인증서 수신중...");
		receiveClientCert();
		System.out.println("개인인증서 수신중 완료");
		System.out.println("CA인증서 수신중...");
		RequestCAServerCert reqCACert = new RequestCAServerCert();
		System.out.println("CA인증서 수신 완료");
		caCert = reqCACert.getCAServerCert();
		System.out.println("개인인증서 검증중...");
		String verifiedMsg = verifyUserCert();
		
		try {
			oos.writeObject(verifiedMsg);
		} catch (IOException e) {
			e.printStackTrace();
		}
		if(verifiedMsg.equals("CertVerifySuccess")){
			System.out.println("개인인증서 검증 완료");
		}else{
			System.out.println("개인인증서 검증 실패");
			return false;
		}
		System.out.println("개인인증메시지 확인");
		if(!verifyUser()){
			System.out.println("개인인증메시지 확인 실패");
			return false;
		}
		
		return true;
		
	}

	private String verifyUserCert() {
		/******************** 사용자 인증서 verify *****************************/
		
		PublicKey caPubKey = caCert.getPublicKey(); // ca 서버 인증서의 공개키 획득		
		try {
			clientCert.checkValidity(new Date());
		} catch (CertificateExpiredException e) {
			System.out.println("CertificateExpiredException");
			return "CertificateExpiredException";
		} catch (CertificateNotYetValidException e) {
			System.out.println("CertificateNotYetValidException");
			return "CertificateNotYetValidException";
		}
		try {
			clientCert.verify(caPubKey);
		} catch (InvalidKeyException e) {
			System.out.println("InvalidKeyException");
			return "InvalidKeyException";
		} catch (CertificateException e) {
			System.out.println("CertificateException");
			return "CertificateException";
		} catch (NoSuchAlgorithmException e) {
			System.out.println("NoSuchAlgorithmException");
			return "NoSuchAlgorithmException";
		} catch (NoSuchProviderException e) {
			System.out.println("NoSuchProviderException");
			return "NoSuchProviderException";
		} catch (SignatureException e) {
			System.out.println("SignatureException");
			return "SignatureException";
		}
		System.out.println("clientCert 인증 완료");
		return "CertVerifySuccess";
	}
	private boolean verifyUser() {
		// 임의의 인증 메시지를 생성하여 사용자의 공개키로 암호화 한 다음  
		// PC프로그램에 인증메세지를 전달한다
		// PC프로그램으로부터 다시 인증메시지를 전달받아서 
		// 암호화 하기 이전의 원문과 같다면 올바른 사용자로 확인한다. 
		
		/******************** 사용자 인증서의 공개키로 인증메세지 암호화 ***********************/
		PublicKey pubKey = clientCert.getPublicKey(); // 사용자 인증서의 공개키 획득
		String plainMsg = getRandomString(10); // 인증메세지 랜덤 생성
		System.out.println("인증메세지" + plainMsg);
		byte[] encryptMsg = EncryptMsg(pubKey, plainMsg.getBytes()); // 인증메세지를
																		// 공개키로
																		// 암호화
		System.out.println("인증메세지 암호화 완료");
	
		/************* 소켓통신을 사용하여 PC 프로그램에 암호화된 인증메세지 전달 ******************/
		try {
			//임시로 파일로 암호화된 인증메세지 저장
			FileOutputStream fos;
			fos = new FileOutputStream("temp.txt");
			fos.write(encryptMsg);
			fos.close();
			
			//사용자에게 인증메세지 전송
			PrintWriter pw = new PrintWriter(socket.getOutputStream());
			pw.println("readyToSendMsg");
			pw.flush();
			FileSendThread fst = new FileSendThread("temp.txt",authmsgFilePort);
			fst.run();
			System.out.println("인증메세지 전송완료");
		/************* 사용자로부터 복호화된 인증메세지 수신 ******************/
			String deMsgStr = (String)ois.readObject();
			if (plainMsg.equals(deMsgStr)){
				System.out.println("사용자 확인 완료");
				return true;
			}
			
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		}
		return false;
	}

	private void receiveClientCert() {
		try {
			InetAddress inetaddr = socket.getInetAddress();  //접속된 소켓의 ip주소값을 가져온다..
			System.out.println(inetaddr.getHostAddress() + " 로 부터 접속하였습니다.");  // 호스트주소값을 가져온다..
			FileReceiveThread trt = new FileReceiveThread(inetaddr.getHostAddress(), serverFilePort, fileClientCert.getName());
			trt.run();
			FileInputStream fis = new FileInputStream(fileClientCert);
			CertificateFactory certificateFactory = CertificateFactory
					.getInstance("X509");
			clientCert = (X509Certificate) certificateFactory
					.generateCertificate(fis);
			fis.close();
			System.out.println("사용자 인증서 불러옴");
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		} catch (CertificateException e) {
			e.printStackTrace();
		}
	}

	/** 원하는 길이만큼 랜덤으로 문자열 생성 **/
	private static String getRandomString(int length) {
		StringBuffer buffer = new StringBuffer();
		Random random = new Random();

		String chars[] = "a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z"
				.split(",");

		for (int i = 0; i < length; i++) {
			buffer.append(chars[random.nextInt(chars.length)]);
		}
		return buffer.toString();
	}

	/** 공개키를 사용하여 메세지 암호화 **/
	private static byte[] EncryptMsg(PublicKey pubKey, byte[] plaintMsg) {

		byte[] plaintext = plaintMsg;
		RSACrypto enigma = new RSACrypto();
		byte[] ciphertext = null;
		try {
			ciphertext = enigma.encrypt(plaintext, pubKey);
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return ciphertext;
	}
	
}